package auth

import (
	"context"
	"gf-vben-admin/internal/config/jwt"
	"gf-vben-admin/internal/model"
	authModel "gf-vben-admin/internal/model/auth"
	"gf-vben-admin/internal/service/system"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/os/gctx"
	"time"
)

var authService *jwt.GfJWTMiddleware

func Auth() *jwt.GfJWTMiddleware {
	return authService
}

func init() {
	realm := g.Cfg().MustGet(gctx.New(), "jwt.realm").String()
	key := g.Cfg().MustGet(gctx.New(), "jwt.key").String()
	timeout := g.Cfg().MustGet(gctx.New(), "jwt.timeout").Int64()
	maxRefresh := g.Cfg().MustGet(gctx.New(), "jwt.max-refresh").Int64()
	identityKey := g.Cfg().MustGet(gctx.New(), "jwt.identity-key").String()
	auth := jwt.New(&jwt.GfJWTMiddleware{
		Realm:           realm,
		Key:             []byte(key),
		Timeout:         time.Minute * time.Duration(timeout),
		MaxRefresh:      time.Minute * time.Duration(maxRefresh),
		IdentityKey:     identityKey,
		TokenLookup:     "header: Authorization, query: token, cookie: jwt",
		TokenHeadName:   "Bearer",
		TimeFunc:        time.Now,
		Authenticator:   Authenticator,
		Unauthorized:    Unauthorized,
		PayloadFunc:     PayloadFunc,
		IdentityHandler: IdentityHandler,
		CacheAdapter:    gcache.NewAdapterRedis(g.Redis()),
	})
	authService = auth
}

// PayloadFunc is a callback function that will be called during login.
// Using this function it is possible to add additional payload data to the webtoken.
// The data is then made available during requests via c.Get("JWT_PAYLOAD").
// Note that the payload is not encrypted.
// The attributes mentioned on jwt.io can't be used as keys for the map.
// Optional, by default no additional data will be set.
func PayloadFunc(data interface{}) jwt.MapClaims {
	claims := jwt.MapClaims{}
	params := data.(map[string]interface{})
	if len(params) > 0 {
		for k, v := range params {
			claims[k] = v
		}
	}
	return claims
}

// IdentityHandler get the identity from JWT and set the identity for every request
// Using this function, by r.GetParam("id") get identity
func IdentityHandler(ctx context.Context) interface{} {
	claims := jwt.ExtractClaims(ctx)
	return claims[authService.IdentityKey]
}

// Unauthorized is used to define customized Unauthorized callback function.
func Unauthorized(ctx context.Context, code int, message string) {
	r := g.RequestFromCtx(ctx)
	_ = r.Response.WriteJson(model.BaseResponse{
		Code:    code,
		Message: message,
	})
	r.ExitAll()
}

// Authenticator is used to validate login parameters.
// It must return user data as user identifier, it will be stored in Claim Array.
// if your identityKey is 'id', your user data must have 'id'
// Check error (e) to determine the appropriate error message.
func Authenticator(ctx context.Context) (interface{}, error) {
	var (
		r  = g.RequestFromCtx(ctx)
		in authModel.LoginParamsReq
	)
	if err := r.Parse(&in); err != nil {
		return "", err
	}

	if user, err := system.User().GetAdminByUsernameAndPwd(ctx, in); err != nil {
		return nil, err
	} else if user != nil {
		return gjson.New(user).Interface(), nil
	}

	return nil, jwt.ErrFailedAuthentication
}
